package com.google.code.jaail.integration

import static org.junit.Assert.fail
import com.google.code.jaail.networks.ChatNetwork
import com.google.code.jaail.JaailKeeper

import static org.corbym.support.SugarMatcher.andWhen

import org.junit.Test
import static org.corbym.support.SugarMatcher.then
import org.corbym.support.SugarMatcher
import static org.corbym.support.SugarMatcher.andAlso
import com.google.code.jaail.user.MessageReceiver
import com.google.code.jaail.messages.Message

import org.hamcrest.Matcher
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import static org.junit.internal.matchers.IsCollectionContaining.hasItem
import org.junit.runner.RunWith

import com.googlecode.yatspec.junit.Table
import com.googlecode.yatspec.junit.Row
import org.junit.After
import com.googlecode.yatspec.junit.SpecRunner
import com.googlecode.yatspec.junit.TableRunner
import org.codehaus.groovy.transform.GroovyASTTransformation
import com.google.code.jaail.networks.protocols.SupportedChatNetwork
import org.junit.BeforeClass
import org.junit.AfterClass
import org.junit.rules.Timeout
import com.googlecode.yatspec.junit.Notes

// uncomment these to get intellij to grab dependencies
/*
@GrabResolver(name='bodart', root='http://repo.bodar.com/')
@Grab(module='yatspec', group='com.googlecode.yatspec', version='90')
*/

@RunWith(TableRunner.class)
class SystemFunctionalRequirementsTest{
    JaailKeeper daemon
    List messagesReceived = [];
    ChatNetwork chatNetwork
    MessageReceiver user
    Message message

    @BeforeClass
    public static setUpFakeChatNetworks(){
        // TODO: this method sets up a "real" fake chat networks to run the tests against
    }
    @After
    void cleanUp(){
        if(daemon){
            daemon.disconnect();
        }
    }

    @AfterClass
    public static void stopFakeChatNetworks(){
        //TODO: this method should shut down all the fake networks that are running
    }

    @Test(timeout=500L)
    @Table([
        @Row(["ABERMUD", "1010"]),
        @Row(["IMC2", "1020"])
    ]
    )
    void "The system must wait for incoming messages on a port for a network"(String network, String portSetting) {
        given:
        aJaailKeeper()
        andAlso theJaailKeeperSupports(aChatNetwork(called(network), withPortSetTo(portSetting)))

        when:
        theJaailKeeper().connect()
        andWhen
        then:
        then theJaailKeeper(), SugarMatcher.is(reportingAConnectionTo(theChatNetwork()))
    }

    @Test(timeout=500L)
    @Table([
        @Row(["ABERMUD", "1010"]),
        @Row(["IMC2", "1020"])
    ]
    )
    void "The system can receive incoming messages on a port for a network"(String network, String portSetting) {
        given:
        aJaailKeeper()
        andAlso theJaailKeeperSupports(aChatNetwork(called(network), withPortSetTo(portSetting)))
        andAlso aUser("listeningTo", theChatNetwork())

        when:
        theJaailKeeper().connect()
        andWhen theChatNetwork().receive(aMessageFor(theUser()))

        then:
        then theUserMessageList(), hasItem(theMessage())
    }

    @Test
    void "The system must present incoming messages from each chat network in a unified, but visually distinct manner"() {
        fail("implement me")
    }

    @Test
    void "The system must report the exact same information to each supported chat network, insofar as each network's protocol supports the information to be reported"() {
        fail("implement me")
    }

    /**
     * TODO: This method should create a real message on the fake chat network, rather than a co-erced map
     */
    Message aMessageFor(def user) {
       this.message =  [from : {}, to:{user.details}, toString : {"message"}] as Message
        return message
    }

    def theUserMessageList() {
        this.messagesReceived
    }

    Matcher reportingAConnectionTo(ChatNetwork chatNetwork) {
        new BaseMatcher(){
            boolean matches(Object o) {
                return daemon.reportConnectionStatus(chatNetwork).isConnected()
            }

            void describeTo(Description description) {
                description.appendText "connection to " + chatNetwork.name + " reported not connected."
            }

        }
    }


    def aUser(def action, def aNetwork) {
        this.user = [
                receive : {message -> messagesReceived << message}
        ] as MessageReceiver
        "$action" this.user, aNetwork
        return user
    }

    def called(String network) {
        return SupportedChatNetwork."$network"
    }

    def listeningTo(def messageReceiver, def network){
        aJaailKeeper().addMessageReceiver(messageReceiver, network)
    }

    def aJaailKeeper() {
        this.daemon = new JaailKeeper()
        return this.daemon
    }
    def theJaailKeeper() {
        this.daemon
    }

    def theJaailKeeperSupports(ChatNetwork chatNetwork) {
        theJaailKeeper().addChatNetwork(chatNetwork)
    }

    def aChatNetwork(ChatNetwork chatNetwork, int portSetting) {
        chatNetwork.setPortSetting(portSetting)
        this.chatNetwork = chatNetwork
        return chatNetwork
    }

    def withPortSetTo(String portNumber) {
        return portNumber as int
    }

    def theChatNetwork() {
        this.chatNetwork
    }

    def theUser() {
        return this.user
    }

    static def from(def something) {
        something
    }

    Message theMessage() {
        this.message
    }


}
